//
// Created by xu on 2024/12/2.
//
#include <stdio.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>

#define ERROR_BUF(ret) \
    char errbuf[1024]; \
    av_strerror(ret, errbuf, sizeof(errbuf));

// 输入缓冲区的大小
#define IN_DATA_SIZE 4096

typedef struct {
    const char *filename;
    int width;
    int height;
    enum AVPixelFormat pixFmt;
    int fps;
} VideoDecodeSpec;


int decode(AVCodecContext *ctx, AVPacket *pkt, AVFrame *frame, FILE *outFile) {
    // 发送压缩数据到解码器
    int ret = avcodec_send_packet(ctx, pkt);
    if (ret < 0) {
        ERROR_BUF(ret);
        av_log(NULL, AV_LOG_INFO, "avcodec_send_packet error\n");
        return ret;
    }

    while (1) {
        // 获取解码后的数据
        ret = avcodec_receive_frame(ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            return 0;
        } else if (ret < 0) {
            ERROR_BUF(ret);
            av_log(NULL, AV_LOG_INFO, "avcodec_receive_frame error\n");
            return ret;
        }

        // 将解码后的数据写入文件
        // 写入Y平面
        fwrite(frame->data[0], 1, frame->linesize[0] * ctx->height, outFile);
        // 写入U平面
        fwrite(frame->data[1], 1, frame->linesize[1] * ctx->height >> 1, outFile);
        // 写入V平面
        fwrite(frame->data[2], 1, frame->linesize[2] * ctx->height >> 1, outFile);
    }
}

void h264Decode(const char *inFilename, VideoDecodeSpec *out) {
    int ret;
    // 用来存放读取的输入文件数据（h264）
    char inDataArray[IN_DATA_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    char *inData;

    // 每次从输入文件中读取的长度
    // 输入缓冲区中，剩下的等待进行解码的有效数据长度
    int inLen;
    // 是否已经读取到了输入文件的尾部
    int inEnd = 0;

    // 文件
    FILE *inFile = NULL;
    FILE *outFile = NULL;

    // 解码器
    AVCodec *codec = NULL;
    // 上下文
    AVCodecContext *ctx = NULL;
    // 解析器上下文
    AVCodecParserContext *parserCtx = NULL;

    // 存放解码前的数据（h264）
    AVPacket *pkt = NULL;
    // 存放解码后的数据（yuv）
    AVFrame *frame = NULL;

    // 获取解码器
    codec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!codec) {
        av_log(NULL, AV_LOG_INFO, "decoder not found\n");
        return;
    }

    // 初始化解析器上下文
    parserCtx = av_parser_init(codec->id);
    if (!parserCtx) {
        av_log(NULL, AV_LOG_INFO, "av_parser_init error\n");
        return;
    }

    // 创建上下文
    ctx = avcodec_alloc_context3(codec);
    if (!ctx) {
        av_log(NULL, AV_LOG_INFO, "avcodec_alloc_context3 error\n");
        goto end;
    }

    // 创建AVPacket
    pkt = av_packet_alloc();
    if (!pkt) {
        av_log(NULL, AV_LOG_INFO, "av_packet_alloc error\n");
        goto end;
    }

    // 创建AVFrame
    frame = av_frame_alloc();
    if (!frame) {
        av_log(NULL, AV_LOG_INFO, "av_frame_alloc error\n");
        goto end;
    }

    // 打开解码器
    ret = avcodec_open2(ctx, codec, NULL);
    if (ret < 0) {
        ERROR_BUF(ret);
        av_log(NULL, AV_LOG_INFO, "avcodec_open2 error\n");
        goto end;
    }

    // 打开文件
    inFile = fopen(inFilename, "rb");
    if (inFile == NULL) {
        av_log(NULL, AV_LOG_INFO, "File open error %s\n", inFilename);
        goto end;
    }
    outFile = fopen(out->filename, "wb");
    if (outFile == NULL) {
        av_log(NULL, AV_LOG_INFO, "File open error %s\n", out->filename);
        goto end;
    }

    // 读取文件数据
    do {
        inLen = fread(inDataArray, 1, IN_DATA_SIZE, inFile);
        // 设置是否到了文件尾部
        inEnd = !inLen;
        // 让inData指向数组的首元素
        inData = inDataArray;
        // 只要输入缓冲区中还有等待进行解码的数据
        while (inLen > 0 || inEnd) {
            // fix: 对于新版本的ffmpeg，应该去掉inEnd，否则出现 [h264 @ 000001644c137440] illegal short term buffer state detected
            // 到了文件尾部（虽然没有读取任何数据，但也要调用av_parser_parse2，修复bug）
            // 经过解析器解析
            ret = av_parser_parse2(parserCtx, ctx, &pkt->data, &pkt->size, (uint8_t *) inData, inLen, AV_NOPTS_VALUE,
                                   AV_NOPTS_VALUE, 0);
            if (ret < 0) {
                ERROR_BUF(ret);
                av_log(NULL, AV_LOG_INFO, "av_parser_parse2 error %s\n", errbuf);
                goto end;
            }

            // 跳过已经解析过的数据
            inData += ret;
            // 减去已经解析过的数据大小
            inLen -= ret;

            av_log(NULL, AV_LOG_INFO, "inEnd: %d  pkt->size: %d ret: %d \n", inEnd, pkt->size, ret);
            // 解码
            if (pkt->size > 0 && decode(ctx, pkt, frame, outFile) < 0) {
                goto end;
            }

            // 如果到了文件尾部
            if (inEnd) {
                break;
            }
        }
    } while (!inEnd);

    // 刷新缓冲区
    decode(ctx, pkt, frame, outFile);

    // 赋值输出参数
    out->width = ctx->width;
    out->height = ctx->height;
    out->pixFmt = ctx->pix_fmt;
    // 用framerate.num获取帧率，并不是time_base.den
    out->fps = ctx->framerate.num;

    end:
    fclose(inFile);
    fclose(outFile);
    av_frame_free(&frame);
    av_packet_free(&pkt);
    av_parser_close(parserCtx);
    avcodec_free_context(&ctx);
}

// 解码出来的数据少一个字节？？？？
int main(int argc, char *argv[]) {
    VideoDecodeSpec out;
    out.filename = "C:/Users/xu/Desktop/out2.yuv";
    h264Decode("C:/Users/xu/Desktop/out.h264", &out);
    av_log(NULL, AV_LOG_INFO, "宽度：%d\n高度：%d\n帧率：%d\n编码格式：%s\n", out.width, out.height, out.fps,
           av_get_pix_fmt_name(out.pixFmt));
    return 0;
}